using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using T.FAS.Runtime.Action.ExpressionPlugin;
using T.FAS.Runtime.Metadata.Entity;
using T.FAS.Runtime.Action.SPI;
//using T.FAS.Authority.MetaData.Dev.Plugin;
//using T.FAS.Authority.Metadata.Entity;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.Common;
using T.FAS.Runtime.DataAccess.Engine;
using T.FAS.Runtime.DataAccess.Service;
using T.FAS.Runtime.DataAccess.SQLDom;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.FAS.Runtime.Expression.Entity;
using T.FAS.Runtime.Expression.Service;
using T.FAS.Runtime.Metadata.Client;

namespace T.FAS.Runtime.Action.Engine
{
    public class CDOActionEngine : IActionExecuteProxy, IActionDataResultBuilder, IActionExecuteHandler
    {
        public void AfterExecute(SPI.ActionExecContext execContext)
        {
            if (execContext != null && execContext.MetaData != null)
            {
                var action = execContext.MetaData as ActionMetaData;
                if (action != null && action.ActionType == Metadata.Entity.ActionType.CDO &&
                    !string.IsNullOrEmpty(action.Method.AfterMethodContent))
                {
                    var parameters = execContext.DataContent;

                    var changeSetType = ChangeSetType.Create;
                    TRecord curTRecord = null;
                    var dataId = string.Empty;
                    var methodType = CDOActionUtil.ConvertToCDOActionBusinessType(action.Method.MethodContent);
                    if (methodType == CDOActionBusinessType.Delete)
                    {
                        changeSetType = ChangeSetType.Delete;
                        dataId = Convert.ToString(parameters["ID"]);
                    }
                    else if (methodType == CDOActionBusinessType.Update)
                    {
                        changeSetType = ChangeSetType.Update;
                        curTRecord = parameters["TRecord"] as TRecord;
                    }
                    else if (methodType == CDOActionBusinessType.Add)
                    {
                        changeSetType = ChangeSetType.Create;
                        curTRecord = parameters["TRecord"] as TRecord;
                    }

                    var result = ExprService.GetInstance.Execute(action.Method.AfterMethodContent, execContext.FASContext, parameters);
                    if (result.State == ResultState.Fail)
                    {
                        throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.ExpressionScriptExecFail,result.RowNum,result.ErrMsg));
                    }
                }
            }
        }

        public void BeforeExecute(SPI.ActionExecContext execContext)
        {
            if (execContext != null && execContext.MetaData != null)
            {
                var action = execContext.MetaData as ActionMetaData;
                if (action != null && action.ActionType == Metadata.Entity.ActionType.CDO &&
                    !string.IsNullOrEmpty(action.Method.BeforeMethodContent))
                {
                    var parameters = execContext.DataContent;

                    var relatedCDOId = action.RelatedMetaDataID;
                    if(!execContext.IsBatchUpdateAction)
                        ConstructChangeSet(execContext);

                    var result = ExprService.GetInstance.Execute(action.Method.BeforeMethodContent, execContext.FASContext, parameters);
                    if (result.State == ResultState.Fail)
                    {
                        throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.ExpressionScriptExecFail, result.RowNum,result.ErrMsg));
                    }
                }
                else if (action != null && action.ActionType == Metadata.Entity.ActionType.CDO &&
                    string.IsNullOrEmpty(action.Method.BeforeMethodContent))
                {
                    if (execContext.OriginalActionMetaData!=null && execContext.OriginalActionMetaData is CompositeDataObject)
                    {
                        CompositeDataObject cdo = execContext.OriginalActionMetaData as CompositeDataObject;
                        if (cdo != null && cdo.IsHaveStateMachine() && !execContext.IsBatchUpdateAction)
                        {
                                ConstructChangeSet(execContext);
                        }
                    }
                }
            }
        }

        public static void ConstructChangeSet(ActionExecContext execContext)
        {
            var action = execContext.MetaData as ActionMetaData;
            if (action == null)
            {
                return;
            }
            var parameters = execContext.DataContent;

            var cdo = execContext.OriginalActionMetaData as CompositeDataObject;

            var changeSetType = ChangeSetType.Create;
            TRecord curTRecord = null;
            var dataId = string.Empty;
            var methodType = CDOActionUtil.ConvertToCDOActionBusinessType(action.Method.MethodContent);
            if (methodType == CDOActionBusinessType.Delete || methodType == CDOActionBusinessType.BatchDelete)
            {
                changeSetType = ChangeSetType.Delete;
                dataId = Convert.ToString(parameters["ID"]);
            }
            else if (methodType == CDOActionBusinessType.Update || methodType == CDOActionBusinessType.BatchUpdate)
            {
                changeSetType = ChangeSetType.Update;
                curTRecord = GetRecordFromParams(parameters);
            }
            else if (methodType == CDOActionBusinessType.Add)
            {
                changeSetType = ChangeSetType.Create;
                curTRecord = GetRecordFromParams(parameters);
            }
            else
            {
                return;
            }
            execContext.FASContext = new FASContext();
            TRecord tRecord = null;
            tRecord = new ChangeSetService().GetChangeSet(cdo, changeSetType, curTRecord, dataId);
            execContext.FASContext.CurrentRecord = tRecord;
        }

        private static TRecord GetRecordFromParams(Dictionary<string,object> paramList)
        {
            var recordData = paramList["TRecord"];
            if (recordData is TRecord)
                return recordData as TRecord;
            else if (recordData is ExpandoObject)
            {
                var data = JsonConvert.DeserializeObject<JObject>(JsonConvert.SerializeObject(recordData));
                return TRecord.BuildRecord(data);
            }
            else
            {
                var data = JsonConvert.DeserializeObject<JObject>(Convert.ToString(recordData));
                return TRecord.BuildRecord(data);
            }
        }
        private static List<TRecord> GetRecordListFromParams(Dictionary<string, object> paramList)
        {
            var recordList = paramList["ListTRecord"];
            if (recordList == null)
                return null;
            if (recordList is List<TRecord>)
                return recordList as List<TRecord>;
            else if (recordList is List<ExpandoObject>)
            {
                var dataList = new List<TRecord>();
                var tmpList = recordList as List<ExpandoObject>;
                tmpList.ForEach(t =>
                {
                    var tmp = JObject.Parse(JsonConvert.SerializeObject(t));
                    var data = TRecord.BuildRecord(tmp);
                    dataList.Add(data);
                });
                return dataList;
            }
            else
            {
                var dataList = new List<TRecord>();
                var tmpList = recordList as List<ExpandoObject>;
                tmpList.ForEach(t =>
                {
                    var tmp = JObject.Parse(Convert.ToString(t));
                    var data = TRecord.BuildRecord(tmp);
                    dataList.Add(data);
                });
                return dataList;

            }
        }

        public TResult BuildActionDataResult(SPI.ActionExecContext context, object dataResult)
        {
            if (dataResult is BatchOperResult)
            {
                var tResult = new TCommonResult<JObject>()
                {
                    ActionCode = context.MetadataCode,
                    ActionName = context.MetadataCode,
                    Count = 0,
                    ResultType = DataType.Record,
                };

                tResult.Schema = new TSchema()
                {
                    Entity = new TEntity()
                };
                tResult.List = new TArray<JObject>(tResult.Schema.Entity);
                var tmp = JsonConvert.DeserializeObject<JObject>(JsonConvert.SerializeObject(dataResult));
                tResult.Record = tmp;
                return tResult;
            }
            else
            {
                return dataResult as TResult;
            }

        }

        public object Execute(SPI.ActionExecContext context)
        {
            if (context.FASContext == null || context.FASContext.CurrentRecord == null)
            {
                if (!context.IsBatchUpdateAction)
                    ConstructChangeSet(context);
            }
            switch (context.ActionType)
            {
                case SPI.ActionType.CDO_ChangeState:
                    return ChangeState(context);
                case SPI.ActionType.CDO_NewInstance:
                    return CDONewInstance(context);
                case SPI.ActionType.CDO_FireEvent:
                    return FireEvent(context);
                case SPI.ActionType.CDO_FireEventWithRecord:
                    return FireEventWithRecord(context);
                case SPI.ActionType.CDO_BatchUpdate:
                    {
                        RefreshRecord(context);
                        return CDOBatchUpdate(context);
                    }
                case SPI.ActionType.CDO_BatchDelete:
                    {
                        return CDOBatchDelete(context);
                    }
                default:
                    return CDOActionExecute(context);
            }
        }

        private object FireEvent(ActionExecContext context)
        {
            var cdoId = Convert.ToString(context.DataContent["CDOID"]);
            var columnCode = Convert.ToString(context.DataContent["ColumnCode"]);
            var eventKey = Convert.ToString(context.DataContent["EventKey"]);
            var billId = Convert.ToString(context.DataContent["BillId"]);
            string source = null;
            if (context.DataContent.ContainsKey("Source"))
            {
                source = Convert.ToString(context.DataContent["Source"]);
            }
            return StateMachineRTPClient.GetInstance().FireEvent(cdoId, columnCode, eventKey, billId, source);
        }

        /// <summary>
        /// 触发事件变换，带Record参数
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private object FireEventWithRecord(ActionExecContext context)
        {
            var cdoId = Convert.ToString(context.DataContent["CDOID"]);
            var columnCode = Convert.ToString(context.DataContent["ColumnCode"]);
            var eventKey = Convert.ToString(context.DataContent["EventKey"]);
            TRecord record = null;
            var recordObj = context.DataContent["Record"];
            if (recordObj != null)
            {
                if (recordObj is TRecord r1)
                {
                    record = r1;
                }
                else if (recordObj is JObject r2)
                {
                    record = TRecord.BuildRecord(recordObj as JObject);
                }
            }
            if (record == null)
                throw new Exception(string.Format(CommonStringClassEngine.CDOActionEngine_FireEventWithRecord));
            string source = null;
            if (context.DataContent.ContainsKey("Source"))
            {
                source = Convert.ToString(context.DataContent["Source"]);
            }
            return StateMachineRTPClient.GetInstance().FireEventWithTRecord(cdoId, columnCode, eventKey, record, source);
        }

        private static object CDOActionExecute(ActionExecContext context)
        {
            var action = ActionRTPUtil.ActionMetadataValid(context.MetaData);
            var paramList = new Dictionary<string, object>();
            if (action.MethodParams != null && action.MethodParams.Count > 0)
            {
                var inParams = action.MethodParams.FindAll(x => x.ParamType == Metadata.Entity.ParamType.In);
                if (inParams != null && inParams.Count > 0)
                {
                    inParams.ForEach(p =>
                    {
                        if (!context.DataContent.ContainsKey(p.Code) && p.DefaultValue == null)
                        {
                            throw new FasException(ActionExecuteExceptionCode.ActionExecuteParamNullError, string.Format(CommonStringClassEngine.ActionExec_ParameterIsNotAssigned,p.Code));
                        }
                        if (context.DataContent.ContainsKey(p.Code))
                            paramList[p.Code] = context.DataContent[p.Code];

                    });
                }
            }
            var extParams = new Dictionary<string, object>();
            // Todo
            //var permissionControls = new List<AuthRuntimeParam>();
            //if (action.IsPermissionControl)
            //{
            //    permissionControls = GetActionAuthRuntimeParams(action.ID);
            //    extParams.Add("CDOAuth", permissionControls);
            //}
            //if (context != null && context.DataContent != null && context.DataContent.Count>0)
            //{
            //    foreach (var dic in context.DataContent)
            //    {
            //        if (!extParams.ContainsKey(dic.Key))
            //            extParams[dic.Key] = context.DataContent[dic.Key];
            //    }
            //}


            var cdo = MetaDataClientService.GetInstance().GetMetaDataContent(action.RelatedMetaDataID) as CompositeDataObject;
            TRecord currentRecord = null;
            if (context.FASContext != null && context.FASContext.CurrentRecord != null)
                currentRecord = context.FASContext.CurrentRecord;
            var method = action.Method.MethodContent;
            switch (method.ToLower())
            {
                case "savedataforcdo":
                    if (currentRecord != null)
                    {
                        return UnifiedDataAccessServiceFactory.GetInstance().SaveDataForCDO(cdo,currentRecord,extParams);
                    }
                    else
                    {
                        var recordData = paramList["TRecord"];
                        if (recordData is TRecord)
                            return UnifiedDataAccessServiceFactory.GetInstance().SaveDataForCDO(cdo, recordData as TRecord, extParams);
                        else if (recordData is ExpandoObject)
                        {
                            var data = JsonConvert.DeserializeObject<JObject>(JsonConvert.SerializeObject(recordData));
                            return UnifiedDataAccessServiceFactory.GetInstance().SaveDataForCDO(cdo, TRecord.BuildRecord(data), extParams);
                        }
                        else
                        {
                            var data = JsonConvert.DeserializeObject<JObject>(Convert.ToString(recordData));
                            return UnifiedDataAccessServiceFactory.GetInstance().SaveDataForCDO(cdo, TRecord.BuildRecord(data), extParams);
                        }
                    }
                case "deletedataforcdo":
                    UnifiedDataAccessServiceFactory.GetInstance().DeleteDataForCDO(cdo, Convert.ToString(paramList["ID"]), extParams);
                    break;
                case "batchupdatedataforcdo":
                case "updatedataforcdo":
                    if (context.IsBatchUpdateAction)
                        extParams["IsBatchUpdate"] = true;
                    if (currentRecord != null)
                    {
                        return UnifiedDataAccessServiceFactory.GetInstance().UpdateDataForCDO(cdo, currentRecord, extParams);
                    }
                    else
                    {
                        var recordData1 = paramList["TRecord"];
                        if (recordData1 is TRecord)
                            return UnifiedDataAccessServiceFactory.GetInstance().UpdateDataForCDO(cdo, recordData1 as TRecord, extParams);
                        else if (recordData1 is ExpandoObject)
                        {
                            var data = JsonConvert.DeserializeObject<JObject>(JsonConvert.SerializeObject(recordData1));
                            return UnifiedDataAccessServiceFactory.GetInstance().SaveDataForCDO(cdo, TRecord.BuildRecord(data), extParams);
                        }
                        else
                        {
                            var data1 = JsonConvert.DeserializeObject<JObject>(Convert.ToString(recordData1));
                            return UnifiedDataAccessServiceFactory.GetInstance().UpdateDataForCDO(cdo, TRecord.BuildRecord(data1), extParams);
                        }
                    }
                case "querydatabyidforcdo":
                    return UnifiedDataAccessServiceFactory.GetInstance().QueryDataForCDO(cdo, Convert.ToString(paramList["ID"]), extParams);
                case "querydatalistforcdo":
                    QueryFilter filter = new QueryFilter();
                    if (paramList.ContainsKey("filter"))
                        filter = paramList["filter"] as QueryFilter;
                    return UnifiedDataAccessServiceFactory.GetInstance().QueryDataForCDO(cdo, filter);
                default:
                    break;
            }
            return null;
        }

        private TResult CDONewInstance(SPI.ActionExecContext context)
        {
            var cdo = context.MetaData as CompositeDataObject;
            if(cdo == null)
            {
                throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.ActionExecFail_ContextCannotBeNull, "CDONewInstance"));
            }
            TRecord record = null;
            if (context.DataContent.ContainsKey("TRecord"))
            {
                var recordData = context.DataContent["TRecord"] as JObject;
                record = TRecord.BuildRecord(recordData);
                if (record == null)
                {
                    record = CreateNewCDOTRecord(cdo);
                }
            }
            else
                record = CreateNewCDOTRecord(cdo);
            var updatedRecord = StateMachineRTPClient.GetInstance().InitRecord(cdo.ID, record);
            return updatedRecord;
        }

        private TRecord CreateNewCDOTRecord(CompositeDataObject cdo)
        {
            TRecord record;
            var exprItem = new ExprItem()
            {
                ID = cdo.ID,
                Code = cdo.Code,
                Name = cdo.Name,
                ExprMetaDataID = cdo.ID,
                IsLeaf = false,
                SortOrder = 0,
                ItemType = ItemType.CDO
            };
            var provider = CommonExpProvider.GetInstance();
            provider.GetMetadata += Provider_GetMetadata;
            record = provider.BuildTResultSchema(exprItem);
            return record;
        }

        private TResult ChangeState(SPI.ActionExecContext context)
        {
            var cdoId = Convert.ToString(context.DataContent["CDOID"]);
            var columnCode = Convert.ToString(context.DataContent["ColumnCode"]);
            var eventKey = Convert.ToString(context.DataContent["EventKey"]);
            var recordData = context.DataContent["TRecord"] as JObject;
            var record = TRecord.BuildRecord(recordData);
            return StateMachineRTPClient.GetInstance().ChangeState(cdoId,columnCode,eventKey,record);
        }

        private IMetaDataContent Provider_GetMetadata(string metaDataID)
        {
            return MetaDataClientService.GetInstance().GetMetaDataContent(metaDataID);
        }

        private object CDOBatchDelete(SPI.ActionExecContext context)
        {
            var ids = JsonConvert.DeserializeObject<List<string>>(Convert.ToString(context.DataContent["IDS"]));
            UnifiedDataAccessServiceFactory.GetInstance().DeleteDataSetForCDO(context.MetaData as CompositeDataObject, ids);
            return null;
        }

        /// <summary>
        /// CDO批量更新
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private object CDOBatchUpdate(SPI.ActionExecContext context)
        {
            var recordDatas = context.DataContent["TRecords"] as List<JObject>;
            var result = new BatchOperResult();
            if (recordDatas != null && recordDatas.Count > 0)
            {
                var cdo = context.MetaData as CompositeDataObject;
                if (cdo == null)
                    throw new Exception(string.Format(CommonStringClassEngine.CDOActionEngine_CDOBatchUpdate,JsonConvert.SerializeObject(context)));
                List<TRecord> records = recordDatas.Select(r => TRecord.BuildRecord(r)).ToList();
                var batchUpdateResult = UnifiedDataAccessServiceFactory.GetInstance().BatchUpdateDataForCDO(cdo, records);

                result.SuccessCount = batchUpdateResult.SuccessCount;
                if (batchUpdateResult.Result)
                    result.State = BatchOperResultState.Success;
                else if (result.SuccessCount == 0)
                    result.State = BatchOperResultState.Fail;
                else
                    result.State = BatchOperResultState.PartialSuccess;
            }
            return result;
        }

        private object CDOBatchUpdate2(SPI.ActionExecContext context)
        {
            var recordDatas = context.DataContent["TRecords"] as List<JObject>;
            var result = new BatchOperResult();
            if (recordDatas != null && recordDatas.Count > 0)
            {
                result.TotalCount = recordDatas.Count;
                result.MetaDataID = context.OriginalActionMetaData.MetaDataID;
                result.MetaDataCode = context.OriginalActionMetaData.MetaDataCode;
                recordDatas.ForEach(record =>
                {
                    var subContext = context.Copy();
                    var updateRecord = TRecord.BuildRecord(record);
                    try
                    {

                        subContext.ActionType = SPI.ActionType.Action;

                        subContext.DataContent = new Dictionary<string, object>();

                        subContext.DataContent.Add("TRecord", updateRecord);

                        var engine = ActionExecuteEngineProvider.CreateTransistEngine(subContext);

                        engine.Execute(subContext);
                    }
                    catch (Exception ex)
                    {
                        result.BatchOperResultDetails.Add(new BatchOperResultDetail()
                        {
                            Result = false,
                            UniqueIdentifier = updateRecord.PKColumnValue,
                            ErrorMsg = ex.Message,
                            ErrorStack = ex.ToString()
                        });
                        result.FailCount++;
                    }
                });
                result.SuccessCount = result.TotalCount - result.FailCount;
                if (result.FailCount == 0)
                    result.State = BatchOperResultState.Success;
                else if (result.SuccessCount == 0)
                    result.State = BatchOperResultState.Fail;
                else
                    result.State = BatchOperResultState.PartialSuccess;
            }
            return result;
        }

        private void RefreshRecord(SPI.ActionExecContext context)
        {
            
        }

        /// <summary>
        /// 获取Action的权限信息
        /// </summary>
        /// <param name="actionId"></param>
        /// <returns></returns>
        // ToDo
        //private static List<AuthRuntimeParam> GetActionAuthRuntimeParams(string actionId)
        //{
        //    try
        //    {
        //        return new ActionPermissionDefinitionManager().GetRuntimeParam(actionId);
        //    }
        //    catch (Exception e)
        //    {
        //        throw new FasException(ActionExecuteExceptionCode.ActionExecuteEngineExecuteError, string.Format(CommonStringClassEngine.CDOActionEngine_GetActionAuthRuntimeParams,actionId), e);
        //    }
        //}

        public void ExecuteError(SPI.ActionExecContext execContext, Exception exception)
        {
            throw new Exception(string.Format(CommonStringClassEngine.CDOActionEngine_ExecuteError,execContext.MetaData.MetaDataCode,exception.ToString()));
        }

        public object ExecuteWithSubEngine(IActionExecuteEngine subEngine, ActionExecContext context)
        {
            throw new NotImplementedException();
        }
    }
}
